<?php
/**
 * application delegate
 * carries the application controller, view object, data, etc.
 * @author bbqq <toughgamer@gmail.com>
 * @package com.blib.app
 * @copyright bbqq qstufie.com
 */
class Blib_App_Delegate extends Blib_Class_Atomic
{
    /**
     * meself
     * @var Blib_App_Delegate
     */
    protected static $_self = null;


    /**
     * the application controller
     * @var Blib_App_Controller
     */
    protected $_controller = null;


    /**
     * internal variables
     * @var array
     */
    protected $_internal = array();


    /**
     * the application view
     * @var Blib_App_View
     */
    protected $_view = null;


    /**
     * the requestUrl
     * @var str
     */
    protected $_requestUrl = null;


    /**
     * the base url
     * @var str
     */
    protected $_baseUrl = null;


    /**
     * site level cache engine
     * note it's a separate engine comparing to other
     * internally used caching engines, so site cache
     * can be easily arranged.
     * @var Blib_Cache_Manager
     */
    protected $_siteCache = null;


    /**
     * get a singleton of delegate
     * @param mixed $controller the controller instance or the class name
     *        in here the controller is the default one we use. it can deal with
     *        non-controller associated actions
     * @param mixed $view the viewer instance or class name
     * @return Blib_App_Delegate
     */
    public static function getInstance(Blib_App_Controller $controller = null, Blib_App_View $view = null)
    {
        // single instance only...
        if (!self::$_self instanceof Blib_App_Delegate) {
            self::$_self = new Blib_App_Delegate($controller, $view);
        }

        return self::$_self;

    }// end getInstance


    /**
     * constructor
     * @param mixed $controller the controller instance or the class name
     * @param mixed $view the viewer instance or class name
     * @Return void
     */
    public function __construct(Blib_App_Controller $controller = null, Blib_App_View $view = null)
    {
        $this->_set('_start_time', microtime(1));

        // only one delegate allowed...
        if (self::$_self instanceof Blib_App_Delegate) {
            return true;
        }

        // otherwise, setup the controller and view
        if (!empty($view)) {
            $this->_set('_default_view', $view);
            Blib::_bp('default view: <pre>' . var_export($view, 1) . '</pre>');
        }

        // set controller and attach view...
        if (!empty($controller)) {
            $controller->setView($view);
            $this->_set('_default_controller', $controller);
            Blib::_bp('default controller: <pre>' . var_export($controller, 1) . '</pre>');
        }

        $this->_getUrl();

    }// end __construct


    /**
     * the main function to start the delegate
     * @return void
     */
    public function main()
    {
        // delegate to site cache, which gets to _main
        $this->_siteCache();

    }// end main

/*======================================[ the big thingy ]======================================*/

    /**
     * set namespace
     * @param str $ns the namespace
     * @return this
     */
    public function setNamespace($ns)
    {
        return $this->_set('_ns', $ns);

    }// end setNamespace


    /**
     * set the router pattern
     * controller/action/id/other
     * @param str $pattern the pattern.
     * @param array $default the default values
     * @return this
     */
    public function setRouter($pattern, $default = array())
    {
        $this->_set('_routerPattern', $pattern);
        $this->_set('_routerDefault', (array) $default);
        return $this;

    }// end setRouter


    /**
     * get the default template
     * follow this rule:
     * use controller name as dirname (lowercase)
     * use function name.php as file name (lowercase as well)
     * @static
     * @return str
     */
    public static function getDefaultTemplate()
    {
        $controls = self::$_self->_get('_RAWCTRLS');
        if (empty($controls)) {
            throw new exception('Undefined Controlls - can not locate default template' . var_export(self::$_self, 1));
        }
        // otherwise
        return $controls['controller'] . '/' . $controls['action'] . '.php';

    }// end getDefaultTemplate


    /**
     * the main function
     * @return void
     */
    protected function _main()
    {
        // 1st. run router and get request objects...
        $this->_route()
             ->_dispatch()
             ->_finish();

    }// end _main


    /**
     * this is where it all finishes
     * this will also determine whether
     * to return all possible info
     */
    protected function _finish()
    {
        // if debug is allowed, do a status dump
        if (Blib::isDebugAllowed()) {
            Blib::_bstatus();
            echo '<b style="font:11px courier">Page Generated in ' .
                 number_format(microtime(1) - $this->_get('_start_time'), 4) .
                 ' seconds</b>';
        }

    }// end _finish


    /**
     * deal with exceptions here
     * use 404 page or not
     * @return void
     */
    protected function _404(exception $e)
    {
        if (Blib::isDebugAllowed()) {
            Blib::dump($e);
        }

    }// end _finish


    /**
     * dispatch the controller
     * @return this
     */
    protected function _dispatch()
    {
        try {
            $con = $this->_get('_CONTROLLER');
            Blib::_bp('initialise the controller: ' . $con);
            if (!class_exists($con)) {
                throw new exception('Controller: ' . $con . ' does not exist');
            }
            $c = new $con;
            $v = $this->_get('_VIEW');
            $c->setView(new $v($this->_get('_ns')));
            // try the action
            $a = $this->_get('_ACTION');
            $p = $this->_get('_PARAMS');
            Blib::_bp('Try to call action: ' . $a . ' with params: <pre>' . var_export($p, 1) . '</pre>');
            if (!method_exists($c, $a)) {
                throw new exception('Action: ' . $a . ' is not supported by controller: ' . $con);
            }
            // run pre-dispatch
            // initialize
            $c->init();
            $c->$a($p);
            Blib::_bp('Function called successfully');
            // post disptach
            $c->finish();
        } catch (exception $e) {
            $this->_404($e);
        }

        return $this;

    }// end _dispatch


    /**
     * run router and
     * get requests
     * @return this
     */
    protected function _route()
    {
        // 1st, use router to find the controller and action names
        $req = $this->_requestUrl;
        $pattern = $this->_get('_routerPattern', 'controller/action');
        $default = $this->_get('_routerDefault', array(
            'controller' => 'default',
            'action'     => 'main'
        ));

        // Blib::dump($req, $pattern, $default);
        // start looping and matching...
        $req      = explode('/', $req);
        $pattern  = explode('/', $pattern);
        $controls = array();
        $params   = array();
        foreach ($pattern as $k => $v) {
            if (!empty($v)) {
                if (!empty($req[$k])) {
                    $controls[$v] = $req[$k];
                    unset($req[$k]);
                } elseif (!empty($default[$v])) {
                    $controls[$v] = $default[$v];
                } else {
                    $controls[$v] = null;
                }
            }
        }
        // finally, if controller/action is still null, use default...
        if (empty($controls['controller'])) {
            $controls['controller'] = 'default';
        }
        if (empty($controls['action'])) {
            $controls['action'] = 'main';
        }

        $params = $controls;
        unset($params['controller'], $params['action']);
        $params = new Blib_App_Param($params);
        $params->addMulti($req);
        // Blib::dump($controls, $params);
        // check view - if exists, use custom, otherwise fall back to Blib_App_View
        $view = $this->getClassName('view_' . $controls['controller']);
        Blib::_bp('Custom view: ' . $view);
        if (!class_exists($view)) {
            $view = 'Blib_App_View';
            Blib::_bp('Custom view not found, revert back to default: Blib_App_View');
        }

        // set controller/action/params to repo
        $this->_set('_CONTROLLER', $this->getClassName('controller_' . $controls['controller']))
             ->_set('_ACTION', $controls['action'])
             ->_set('_PARAMS', $params)
             ->_set('_VIEW', $view)
             ->_set('_RAWCTRLS', $controls);
        unset($controls, $req, $params);
        // self dump!
        return $this;

    }// end _route


/*======================================[ utility ]======================================*/

    /**
     * site level cache
     * the fastest way to cache the whole thing
     * @return void
     */
    private function _siteCache()
    {
        // debug
        Blib::_bp('Check site cache');
        if ($this->getConfig('cache.site.enabled', false)) {
            // site cache is enabled... use cache adaptor specified in site cache
            $adaptor = $this->getConfig('cache.site.adaptor');
            $options = $this->getConfig('cache.site.options');
            Blib::_bp($options);
            if (!empty($options)) {
                // reason for new instance: this is for site wide cache only
                $this->_siteCache = new Blib_Cache_Manager($adaptor, $options);
                // start checking the stuff...
                $id  = md5($this->_baseUrl . $this->_requestUrl . serialize($_GET));
                Blib::_bp('Verifying site cache by ID: ' . $id);
                $src = $this->_siteCache->load($id);
                if (empty($src)) {
                    // not found
                    Blib::_bp('Cache Not Found, rebuilding it by calling App_Delegate::_main()');
                    // make site cache by calling _main
                    ob_start();
                    $this->_main();
                    $src = ob_get_clean();
                    // if not empty, cache it in, then exit
                    if (!empty($src)) {
                        $this->_siteCache->cache($id, $src);
                    }
                } else {
                    // src is good.
                    Blib::_bp('Cache found, using cached content.');
                }

                // then do it here...
                echo $src;
                return true;
            }
        } else {
            Blib::_bp('Site cache is not enabled');
        }

        // other cases? just call main.
        $this->_main();

    }// end _siteCache


    /**
     * get the url call relative to itself
     */
    private function _getUrl()
    {
        if (empty($this->_baseUrl)) {
            $this->_baseUrl    = dirname($_SERVER['PHP_SELF']) . '/';
            // trim ?query strings
            $req = $_SERVER['REQUEST_URI'];
            $pos = strpos($req, '?');
            if ($pos !== false) {
                $req = substr($req, 0, $pos);
            }
            $this->_requestUrl = str_replace($this->_baseUrl, '', $req);
        }
        Blib::_bp('Request URL: ' . $this->_requestUrl . ' <<< Base URL:' . $this->_baseUrl);

    }// end _getUrl


    /**
     * get the base url out
     * @param str $url the url to attach
     * @return void
     */
    public function getBaseUrl($url = null)
    {
        // if url is not null, check if it's an external one (with //), if so, return straight
        // otherwise, attach it.
        if (strpos($url, '//') !== false) {
            return $url;
        }

        return $this->_baseUrl . ltrim('/', $url);

    }// end getBaseUrl


    /**
     * get the request url in plain str
     * @return str
     */
    public function getRequestUrl()
    {
        return $this->_requestUrl;

    }// end getRequestUrl

/*======================================[ view/control getters ]======================================*/

    /**
     * get view
     * @return Blib_App_View
     */
    public function getView()
    {
        return $this->_view;

    }// end getView


    /**
     * to get views...
     * @return Blib_App_View
     */
    public static function view()
    {
        // only when view is available
        if (self::$_self instanceof Blib_App_Delegate) {
            return self::$_self->getView();
        }

    }// end view


    /**
     * get Controller
     * @return Blib_App_Controller
     */
    public function getController()
    {
        return $this->_controller;

    }// end getController


    /**
     * to get controller...
     * @return Blib_App_Controller
     */
    public static function controller()
    {
        // only when view is available
        if (self::$_self instanceof Blib_App_Delegate) {
            return self::$_self->getController();
        }

    }// end controller


    /**
     * internal registry
     * @param str $key the key
     * @param mixed $val the value
     * @return this
     */
    protected function _set($key, $val)
    {
        $this->_internal[$key] = $val;
        return $this;

    }// end _set


    /**
     * get internal registry
     * @param str $key the key
     * @param mixed $default the default value
     * @return mixed
     */
    protected function _get($key, $default = null)
    {
        if (!empty($this->_internal[$key])) {
            return $this->_internal[$key];
        }

        return $default;

    }// end _get

/*======================================[ customise class ]======================================*/

    /**
     * get class name by given name/ns/folder name
     * @param str $name the body
     * @param str $ns the namespace, if null, get from the default _ns
     * @return str the full class name
     */
    public function getClassName($name, $ns = '')
    {
        if (strpos($name, '_') !== false) {
            $name = trim(str_replace('_', ' ', $name));
        }

        if (empty($ns)) {
            $ns = $this->_get('_ns');
        }

        // now...combine them up
        $class = ucwords(strtolower($ns . ' ' . $name));
        // replace back
        return str_replace(' ', '_', $class);

    }// end getClassName

}// end Blib_App_Delegate
?>